home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Financial / Stopwatch2.3 / Source / ClientInfo.m < prev    next >
Text File  |  1995-06-12  |  8KB  |  424 lines

  1. /*
  2.  * For legal stuff see the file COPYRIGHT
  3.  */
  4. #import <stdlib.h>
  5. #import <objc/hashtable.h>
  6. #import "ClientInfo.h"
  7. #import "Controller.h"
  8. #import "Session.h"
  9. #import "Expense.h"
  10.  
  11. @interface ClientInfo(PRIVATE)
  12. - (void)setupSessionList;
  13. - (void)setupExpenseList;
  14. @end
  15.  
  16. @implementation ClientInfo
  17.  
  18. - init
  19. {
  20.   [super init];
  21.   [self setupSessionList];
  22.   [self setupExpenseList];
  23.   return self;
  24. }
  25.  
  26. - free
  27. {
  28.   [[sessionList freeObjects] free];
  29.   sessionList = nil;
  30.   [[expenseList freeObjects] free];
  31.   expenseList = nil;
  32.   return [super free];
  33. }
  34.  
  35. - (void)computeTotalMins
  36. {
  37.   int i, count = [sessionList count];
  38.  
  39.   totalMins = 0;
  40.   for ( i = 0; i < count; i++ )
  41.     totalMins += [[sessionList objectAt:i] minutes];
  42. }
  43.  
  44. /*
  45.  * Called by the controller when a new session has been started.
  46.  */
  47. - addSession:session
  48. {
  49.   [sessionList addObject:session];
  50.   totalMins += [session minutes];
  51.   return self;
  52. }
  53.  
  54. - deleteSession:session
  55. {
  56.   totalMins -= [session minutes];
  57.   return [sessionList removeObject:session];
  58. }
  59.  
  60. /*
  61.  * Delete all session and expense data
  62.  */
  63. - (void)deleteSessionsAndExpenses
  64. {
  65.   [sessionList freeObjects];
  66.   [expenseList freeObjects];
  67.   totalMins = 0.0;
  68. }
  69.  
  70. - (const char *)lastDescription
  71. {
  72.   const char *str = [[sessionList objectAt:0] description];
  73.   return str ? str : "";
  74. }
  75.  
  76. - (int)sessionCount
  77. {
  78.   return [sessionList count];
  79. }
  80.  
  81. - sessionAt:(int)position
  82. {
  83.   return [sessionList objectAt:position];
  84. }
  85.  
  86. /*
  87.  * Called by the controller when a new session has been started.
  88.  */
  89. - addExpense:expense
  90. {
  91.   [expenseList addObject:expense];
  92.   return self;
  93. }
  94.  
  95. - deleteExpense:expense
  96. {
  97.   return [expenseList removeObject:expense];
  98. }
  99.  
  100. - (void)deleteAllExpenses
  101. {
  102.   [expenseList freeObjects];
  103. }
  104.  
  105. - (int)expenseCount
  106. {
  107.   return [expenseList count];
  108. }
  109.  
  110. - expenseAt:(int)position
  111. {
  112.   return [expenseList objectAt:position];
  113. }
  114.  
  115. - (void)sortSessions
  116. {
  117.   [sessionList sort];
  118. }
  119.  
  120. - (void)sortExpenses
  121. {
  122.   [expenseList sort];
  123. }
  124.  
  125. - (const char *)shortName
  126. {
  127.   return shortName;
  128. }
  129.  
  130. - (const char *)clientName
  131. {
  132.   return clientName;
  133. }
  134.  
  135. - (const char *)contactName
  136. {
  137.   return contactName;
  138. }
  139.  
  140. - (const char *)street
  141. {
  142.   return street;
  143. }
  144.  
  145. - (const char *)city
  146. {
  147.   return city;
  148. }
  149.  
  150. - (const char *)addrState    /* "state" is a defined method in Cell...*/
  151. {
  152.   return state;
  153. }
  154.  
  155. - (const char *)zipCode
  156. {
  157.   return zipCode;
  158. }
  159.  
  160. - (const char *)faxNumber
  161. {
  162.   return faxNumber;
  163. }
  164.  
  165. - (const char *)emailAddr
  166. {
  167.   return emailAddr;
  168. }
  169.  
  170. - (float)hourlyRate
  171. {
  172.   return hourlyRate;
  173. }
  174.  
  175. - (int)totalMins
  176. {
  177.   return totalMins;
  178. }
  179.  
  180. /*
  181.  * This is necessary to have the number we use in calculations be
  182.  * (almost) identical to that which is printed on the invoices. It
  183.  * turns out for a total number of hours such as 76.466667, which
  184.  * prints and displays as 76.47 causes enough error when multiplied
  185.  * by an hourly rate that clients noticed the difference. (It happened
  186.  * to be in their favor. This adjusts it to remove the discrepancy.
  187.  * It happens to result in a few more cents in our favor, by the way!
  188.  */
  189. - (float)totalHours
  190. {
  191.   return ((int)((totalMins * 100)/60 + 0.5))/100.0; /* round up then truncate */
  192. }
  193.  
  194. - (float)totalBillable
  195. {
  196.   return hourlyRate * [self totalHours];
  197. }
  198.  
  199. /*
  200.  * Compute this on the fly.
  201.  */
  202. - (float)totalExpenses
  203. {
  204.   int i, count = [expenseList count];
  205.   float amount = 0.0;
  206.  
  207.   for ( i = 0; i < count; i++ ) {
  208.     Expense *expense = [expenseList objectAt:i]; 
  209.     amount += [expense amount];
  210.   }
  211.  
  212.   return amount;
  213. }
  214.  
  215. - setShortName:(const char *)str
  216. {
  217.   freeAndCopy( &shortName, str );
  218.   return self;
  219. }
  220.  
  221. - setClientName:(const char *)str
  222. {
  223.   freeAndCopy( &clientName, str );
  224.   return self;
  225. }
  226.  
  227. - setContactName:(const char *)str
  228. {
  229.   freeAndCopy( &contactName, str );
  230.   return self;
  231. }
  232.  
  233. - setStreet:(const char *)str
  234. {
  235.   freeAndCopy( &street, str );
  236.   return self;
  237. }
  238.  
  239. - setCity:(const char *)str
  240. {
  241.   freeAndCopy( &city, str );
  242.   return self;
  243. }
  244.  
  245. - setAddrState:(const char *)str
  246. {
  247.   freeAndCopy( &state, str );
  248.   return self;
  249. }
  250.  
  251. - setZipCode:(const char *)str
  252. {
  253.   freeAndCopy( &zipCode, str );
  254.   return self;
  255. }
  256.  
  257. - setFaxNumber:(const char *)str
  258. {
  259.   freeAndCopy( &faxNumber, str );
  260.   return self;
  261. }
  262.  
  263. - setEmailAddr:(const char *)str
  264. {
  265.   freeAndCopy( &emailAddr, str );
  266.   return self;
  267. }
  268.  
  269. - setHourlyRate:(float)value
  270. {
  271.   hourlyRate = value;
  272.   return self;
  273. }
  274.  
  275. - setTotalMins:(int)value
  276. {
  277.   totalMins = value;
  278.   return self;
  279. }
  280.  
  281. /*
  282.  * Compress out consecutive sessions with identical descriptions.
  283.  * This method assumes the list stores sessions in reverse 
  284.  * chronological order.
  285.  */
  286. - (void)compactSessions
  287. {
  288.   int i, earliestPos = [sessionList count] - 1;
  289.   Session *earliest = [sessionList objectAt:earliestPos];
  290.  
  291.   for ( i = earliestPos - 1; i >= 0; --i ) {
  292.     Session *session;
  293.  
  294.     session = [sessionList objectAt:i];
  295.  
  296.     /* Check for consecutive sessions on the same date with the same text */
  297.     if ( strcmp([session description], [earliest description]) == 0 &&
  298.      strcmp([session startDateString], [earliest startDateString]) == 0 ) {
  299.       [earliest setMinutes:[earliest minutes] + [session minutes]];
  300.       [[sessionList removeObjectAt:i] free];
  301.     } else
  302.       earliest = session;    /* this is our new base case */
  303.   }
  304. }
  305.  
  306. - (void)exportToFile:(FILE *)fp
  307. {
  308.   int i, count = [sessionList count];
  309.  
  310.   for ( i = 0; i < count; i++ ) {
  311.     Session *session;
  312.  
  313.     session = [sessionList objectAt:i];
  314.     fprintf( fp, "%s%c%s%c%s%c%d%c%s\n",
  315.         shortName,                DELIMITER,
  316.         [session startDateString], DELIMITER,
  317.         [session startTimeString], DELIMITER,
  318.         [session minutes],            DELIMITER,
  319.         [session description] );
  320.   }
  321. }
  322.  
  323. - read: (NXTypedStream *) stream
  324. {
  325.   extern int FileVersion;
  326.  
  327.   switch ( FileVersion ) {
  328.   case 0:
  329.     NXReadTypes( stream, "********f",
  330.         &clientName, &contactName, &street, &city, &state, &zipCode,
  331.         &faxNumber, &emailAddr, &hourlyRate );
  332.  
  333.     /* make the short name the same as the full name initially */
  334.     shortName = NXCopyStringBuffer(clientName);
  335.     break;
  336.  
  337.   default:
  338.     /* Version 1 added shortName for client */
  339.     NXReadTypes( stream, "*********f",
  340.         &shortName, &clientName, &contactName,
  341.         &street, &city, &state, &zipCode,
  342.         &faxNumber, &emailAddr, &hourlyRate );
  343.     break;
  344.   }
  345.  
  346.   if ( ! sessionList )
  347.     [self setupSessionList];
  348.  
  349.   [sessionList read:stream];
  350.   [sessionList sort];
  351.  
  352.   if ( ! expenseList )
  353.     [self setupExpenseList];
  354.  
  355.   if ( FileVersion >= 2 ) {
  356.     [expenseList read:stream];
  357.     [expenseList sort];
  358.   }
  359.  
  360.   [self computeTotalMins];
  361.   return self;
  362. }
  363.  
  364. - write:(NXTypedStream *) stream
  365. {
  366.   NXWriteTypes( stream, "*********f",
  367.            &shortName, &clientName, &contactName,
  368.            &street, &city, &state, &zipCode,
  369.            &faxNumber, &emailAddr, &hourlyRate );
  370.     
  371.   [sessionList write:stream];
  372.   [expenseList write:stream];
  373.   return self;
  374. }
  375.  
  376. @end
  377.  
  378.  
  379. @implementation ClientInfo(PRIVATE)
  380.  
  381. /*
  382.  * Compare the dates (and if equal, the times) of two
  383.  * sessions and return the proper value to achieve a
  384.  * reverse chronological sort.
  385.  */
  386. - (int)compareSessions:(Session *)obj1 :(Session *)obj2
  387. {
  388.   int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
  389.   
  390.   if ( date1 == date2 )
  391.     return ( [obj1 timeValue] > [obj2 timeValue] ? -1 : 1 );
  392.  
  393.   return ( date1 > date2 ? -1 : 1 );
  394. }
  395.  
  396. - (int)compareExpenses:(Expense *)obj1 :(Expense *)obj2
  397. {
  398.   int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
  399.   
  400.   return ( date1 > date2 ? -1 : 1 );
  401. }
  402.  
  403. - (void)setupSessionList
  404. {
  405.   if ( ! sessionList ) {
  406.     sessionList = [[SortList alloc] init];
  407.     [sessionList setDelegate:self];
  408.     [sessionList setAutoSort:YES];
  409.     [sessionList useComparisonMethod:@selector(compareSessions::)];
  410.   }
  411. }
  412.  
  413. - (void)setupExpenseList
  414. {
  415.   if ( ! expenseList ) {
  416.     expenseList = [[SortList alloc] init];
  417.     [expenseList setDelegate:self];
  418.     [expenseList setAutoSort:YES];
  419.     [expenseList useComparisonMethod:@selector(compareExpenses::)];
  420.   }
  421. }
  422.  
  423. @end
  424.